package com.foo.common.base.utils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.List;
import java.util.Properties;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;
import org.junit.Assert;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;

import com.foo.common.base.pojo.ClassFieldForMustache;
import com.foo.common.base.pojo.ClassFieldGetAndSetForMustache;
import com.foo.common.base.pojo.OracleUserTabColumnsModel;
import com.github.mustachejava.DefaultMustacheFactory;
import com.github.mustachejava.Mustache;
import com.github.mustachejava.MustacheFactory;
import com.google.common.base.CaseFormat;
import com.google.common.collect.Lists;

/**
 * 警告：：：目前对于mysql的主键生成策略是默认单主键，并且名称一定为id。复合主键的情况有需要再进行完善
 * 
 * @author Steve
 *
 */
public enum DatabaseTablesHelper {

	INSTANCE;

	// private static String myDialect = "mysql";
	private static String myDialect = "oracle";

	private static Logger logger = LoggerFactory
			.getLogger(DatabaseTablesHelper.class);

	private static final String tableName = "ITMS_ORDER_ERRORCODE";
	private static final String hibernate_oracle_properties = "hibernate-localhost-oracle.properties";
	private static final String hibernate_mysql_properties = "hibernate-localhost-mysql.properties";

	private String generatedJavaFileDirectory = FooUtils.global_test_file_working_directory_str
			+ "\\autoGeneratedModel";

	public static void main(String[] args) throws Exception {
		if (myDialect.equals("mysql")) {
			DatabaseTablesHelper.INSTANCE
					.generateWithMustache(INSTANCE.getMysqlSqlResult());
		} else {
			DatabaseTablesHelper.INSTANCE.getOracleSqlResult();
		}
	}

	private void generateWithMustache(List<Object[]> fieldsList)
			throws Exception {
		MustacheFactory mf = new DefaultMustacheFactory();

		String myPackage = "com.feiynn.tenmalife.model";
		String myClassName = CaseFormat.UPPER_UNDERSCORE
				.to(CaseFormat.UPPER_CAMEL, tableName.toUpperCase());

		boolean hasDateField = false;
		String myFieldName;
		String myFieldNameFirstUpper;
		String myFieldType;
		String myColumnName;
		boolean myNullable;
		String myFieldTypeInitExp;
		String myLengthExp;

		HashMap<String, Object> scopes = new HashMap<String, Object>();
		scopes.put("myPackage", myPackage);
		scopes.put("myTableName", tableName.toUpperCase());
		scopes.put("myClassName", myClassName);

		List<ClassFieldForMustache> fields = Lists.newArrayList();
		ClassFieldForMustache filed;

		List<ClassFieldGetAndSetForMustache> fieldsGetAndSet = Lists
				.newArrayList();
		ClassFieldGetAndSetForMustache fieldGetAndSetObject;

		for (Object[] result : fieldsList) {
			myColumnName = result[0].toString();
			myFieldName = CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL,
					myColumnName.toUpperCase());

			// TODOO 这里默认id为主键，所以不进行id的解析了
			if (myFieldName.toLowerCase().equals("id")) {
				continue;
			}

			myFieldNameFirstUpper = StringUtils.capitalize(myFieldName);

			myFieldType = getMyFieldTypeForMysql(result[1].toString());
			if (myFieldType.equals("Date")) {
				hasDateField = true;
			}

			myFieldTypeInitExp = getMyFieldTypeInitExpForMysql(
					result[1].toString());
			myLengthExp = getMyLengthExpForMysql(result[1].toString());

			myNullable = result[2].toString().toLowerCase().equals("yes") ? true
					: false;

			filed = new ClassFieldForMustache();
			filed.setMyColumnName(myColumnName);
			filed.setMyFieldName(myFieldName);
			filed.setMyFieldType(myFieldType);
			filed.setMyFieldTypeInitExp(myFieldTypeInitExp);
			filed.setMyLengthExp(myLengthExp);
			filed.setMyNullable(myNullable);
			fields.add(filed);

			fieldGetAndSetObject = new ClassFieldGetAndSetForMustache();
			fieldGetAndSetObject.setMyFieldName(myFieldName);
			fieldGetAndSetObject
					.setMyFieldNameFirstUpper(myFieldNameFirstUpper);
			fieldGetAndSetObject.setMyFieldType(myFieldType);
			fieldsGetAndSet.add(fieldGetAndSetObject);

		}

		// fieldGetAndSetObject = new ClassFieldGetAndSetForMustache();
		// fieldGetAndSetObject.setMyFieldName("id");
		// fieldGetAndSetObject.setMyFieldNameFirstUpper("Id");
		// fieldGetAndSetObject.setMyFieldType("String");
		// fieldsGetAndSet.add(fieldGetAndSetObject);

		scopes.put("hasDateField", hasDateField);
		scopes.put("fields", fields);
		scopes.put("fieldsGetAndSet", fieldsGetAndSet);

		Mustache mustache = mf.compile("template-generic-mysql-model.mustache");

		Writer writer = new StringWriter();

		mustache.execute(writer, scopes);
		writer.flush();

		String result = writer.toString();
		logger.info("result is: \n\n\n {}", result);

		FooUtils.forceMkdir(generatedJavaFileDirectory);
		File resultFile = new File(
				generatedJavaFileDirectory + "\\" + myClassName + ".java");

		IOUtils.write(result, new FileOutputStream(resultFile), "utf-8");
		logger.info("generate file:{} on path:{}", resultFile.getName(),
				generatedJavaFileDirectory);
	}

	/**
	 * token passed in will be the following formats:
	 * 
	 * varchar(255),int(11),datetime
	 * 
	 * @param token
	 * @return
	 */
	private String getMyFieldTypeInitExpForMysql(String token) {
		String type = getMyFieldTypeForMysql(token);
		if (type.equals("String")) {
			return " \"\" ";
		} else if (type.equals("int")) {
			return " 0 ";
		} else if (type.equals("Date")) {
			return " new Date() ";
		} else if (type.equals("double")) {
			return " 0 ";
		} else {
			throw new UnsupportedOperationException(token);
		}
	}

	/**
	 * token passed in will be the following formats:
	 * 
	 * varchar(255),int(11),datetime
	 * 
	 * @param token
	 * @return
	 */
	private String getMyLengthExpForMysql(String token) {
		String type = getMyFieldTypeForMysql(token);
		if (type.equals("String")) {
			return ", length = " + FooUtils.extractIntegerFromString(token)
					+ " ";
		}
		return "";
	}

	/**
	 * token passed in will be the following formats:
	 * 
	 * varchar(255),int(11),datetime
	 * 
	 * @param token
	 * @return
	 */
	private String getMyFieldTypeForMysql(String token) {
		if (token.contains("varchar")) {
			return "String";
		} else if (token.contains("int")) {
			return "int";
		} else if (token.contains("datetime")) {
			return "Date";
		} else if (token.contains("decimal")) {
			return "double";
		} else if (token.contains("date")) {
			return "Date";
		} else {
			throw new UnsupportedOperationException(token);
		}
	}

	private List<Object[]> getMysqlSqlResult() throws Exception {
		ClassPathResource myPath = new ClassPathResource(
				hibernate_mysql_properties);

		Assert.assertTrue(myPath.exists());

		Properties p = new Properties();
		p.load(myPath.getInputStream());

		Configuration configuration = new Configuration().setProperties(p);

		ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder()
				.applySettings(configuration.getProperties()).build();

		SessionFactory myFactory = configuration
				.addAnnotatedClass(OracleUserTabColumnsModel.class)
				.buildSessionFactory(serviceRegistry);

		Session session = myFactory.openSession();

		Transaction tx = null;

		tx = session.beginTransaction();
		@SuppressWarnings("unchecked")
		List<Object[]> myList = (List<Object[]>) session
				.createSQLQuery(" desc " + tableName + "").list();
		tx.commit();
		session.close();
		myFactory.close();

		return myList;

	}

	/**
	 * 生成pojo的filed描述语句，一般情况下已经够用
	 * 
	 * @param session
	 * @throws IOException
	 */
	void doInTransaction(Session session) throws IOException {

		// String tableName = "STB_UPGRADE_STRATEGY";

		// 是否需要添加注解。如果不需要getObject操作的话，可以禁用这项。
		boolean withAnnotation = true;

		@SuppressWarnings("unchecked")
		List<OracleUserTabColumnsModel> myList = session.createQuery(
				" FROM OracleUserTabColumnsModel WHERE table_name='" + tableName
						+ "' ")
				.list();

		String myDataType = "";
		String oriColumnName = "";
		String javaCamelColumnName = "";

		StringBuilder sb = new StringBuilder();

		sb.append("@@Entity");
		sb.append("@Table(name = \"" + tableName + "\")");
		sb.append("\n");
		sb.append(FooUtils.getSeparateLine());
		sb.append("\n");

		for (OracleUserTabColumnsModel oracleUserTabColumnsModel : myList) {
			myDataType = oracleUserTabColumnsModel.getData_type();
			oriColumnName = oracleUserTabColumnsModel.getPk().getColumn_name()
					.toUpperCase();
			javaCamelColumnName = CaseFormat.UPPER_UNDERSCORE
					.to(CaseFormat.LOWER_CAMEL, oriColumnName);

			if (withAnnotation) {
				sb.append("@Column(name = \"" + oriColumnName + "\")");
				sb.append("\n");
			}

			sb.append("private");

			if (myDataType.contains("date") || myDataType.contains("time")) {

				sb.append(" Date " + javaCamelColumnName + " = new Date();");
			} else if (myDataType.contains("number")) {
				sb.append(" int " + javaCamelColumnName + " = 0;");
			} else if (myDataType.contains("double")) {
				sb.append(" double " + javaCamelColumnName + " = 0;");
			} else if (myDataType.contains("varchar")) {
				sb.append(" String " + javaCamelColumnName + " = \"\";");
			} else {
				sb.append(" String " + javaCamelColumnName + " = \"\";");
			}
			System.out.println(sb.toString());
			sb = new StringBuilder();
		}
		logger.info("generate end.{}", FooUtils.getSeparateLine());
	}

	public SessionFactory buildSessionFactory() {
		try {
			// Create the SessionFactory from hibernate.cfg.xml
			ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder()
					.build();
			return new Configuration().configure()
					.buildSessionFactory(serviceRegistry);
		} catch (Throwable ex) {
			// Make sure you log the exception, as it might be swallowed
			System.err.println("Initial SessionFactory creation failed." + ex);
			throw new ExceptionInInitializerError(ex);
		}
	}

	private void getOracleSqlResult() throws IOException {

		ClassPathResource myPath = new ClassPathResource(
				hibernate_oracle_properties);

		Assert.assertTrue(myPath.exists());

		Properties p = new Properties();
		p.load(myPath.getInputStream());

		Configuration configuration = new Configuration().setProperties(p);

		ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder()
				.applySettings(configuration.getProperties()).build();

		SessionFactory myFactory = configuration
				.addAnnotatedClass(OracleUserTabColumnsModel.class)
				.buildSessionFactory(serviceRegistry);

		Session session = myFactory.openSession();

		Transaction tx = null;

		// See:http://docs.jboss.org/hibernate/orm/4.1/manual/en-US/html/ch13.html#transactions-demarcation-nonmanaged
		try {
			tx = session.beginTransaction();
			// tx.setTimeout(200);
			doInTransaction(session);
			tx.commit();
		} catch (RuntimeException e) {
			if (tx != null)
				tx.rollback();
			throw e;
		} finally {
			session.close();
			myFactory.close();
		}
	}

}
